import cv2
import numpy as np
#import wiringpi
import serial
import time

# 初始化 WiringPi 库
#wiringpi.wiringPiSetup()

# 定义 GPIO 引脚
BUTTON_PIN_UPDATE = 10  # 更新矩阵的按键
BUTTON_PIN_WHITE = 9    # 玩家选择白棋的按键
BUTTON_PIN_BLACK = 13   # 玩家选择黑棋的按键

# 设置引脚模式为输入模式
#wiringpi.pinMode(BUTTON_PIN_UPDATE, wiringpi.INPUT)
#wiringpi.pinMode(BUTTON_PIN_WHITE, wiringpi.INPUT)
#wiringpi.pinMode(BUTTON_PIN_BLACK, wiringpi.INPUT)

# 启用内置上拉电阻
#wiringpi.pullUpDnControl(BUTTON_PIN_UPDATE, wiringpi.PUD_UP)
#wiringpi.pullUpDnControl(BUTTON_PIN_WHITE, wiringpi.PUD_UP)
#wiringpi.pullUpDnControl(BUTTON_PIN_BLACK, wiringpi.PUD_UP)

# 初始化串口
#uart = serial.Serial(
    #port="/dev/ttyS0",
    #baudrate=115200,
    #bytesize=serial.EIGHTBITS,
    #parity=serial.PARITY_NONE,
    #stopbits=serial.STOPBITS_ONE,
#)

# 定义棋子和空位
WHITE = 'W'  # 白棋
BLACK = 'B'  # 黑棋
EMPTY = ' '  # 空位

# 初始化玩家和机器的棋子颜色
player_piece = None
machine_piece = None

# 定义棋盘大小
BOARD_SIZE = 3

# 定义全局矩阵来存储棋盘状态
global_matrix = np.full((BOARD_SIZE, BOARD_SIZE), EMPTY, dtype='<U1')

# 编号映射表
position_map = {
    (0, 0): 1,
    (0, 1): 4,
    (0, 2): 7,
    (1, 0): 2,
    (1, 1): 5,
    (1, 2): 8,
    (2, 0): 3,
    (2, 1): 6,
    (2, 2): 9,
}

# 消抖函数
def debounce(pin, delay=0.05):
    initial_state = wiringpi.digitalRead(pin)
    time.sleep(delay)
    final_state = wiringpi.digitalRead(pin)
    return initial_state == final_state == wiringpi.LOW

# 打印棋盘
def print_board(board):
    for row in board:
        print(' | '.join(row))
        print('-' * (BOARD_SIZE * 4 - 3))

# 检查是否有空位
def has_empty(board):
    return EMPTY in board

# 检查是否有玩家赢了
def check_win(board, player):
    # 检查行
    for row in board:
        if all(cell == player for cell in row):
            return True
    # 检查列
    for col in range(BOARD_SIZE):
        if all(board[row][col] == player for row in range(BOARD_SIZE)):
            return True
    # 检查对角线
    if all(board[i][i] == player for i in range(BOARD_SIZE)) or all(board[i][BOARD_SIZE - 1 - i] == player for i in range(BOARD_SIZE)):
        return True
    return False

# 极小极大算法实现
def minimax(board, depth, maximizing_player):
    if check_win(board, machine_piece):
        return 1
    elif check_win(board, player_piece):
        return -1
    elif not has_empty(board):
        return 0

    if maximizing_player:
        max_eval = float('-inf')
        for i in range(BOARD_SIZE):
            for j in range(BOARD_SIZE):
                if board[i][j] == EMPTY:
                    board[i][j] = machine_piece
                    eval = minimax(board, depth + 1, False)
                    board[i][j] = EMPTY
                    max_eval = max(max_eval, eval)
        return max_eval
    else:
        min_eval = float('inf')
        for i in range(BOARD_SIZE):
            for j in range(BOARD_SIZE):
                if board[i][j] == EMPTY:
                    board[i][j] = player_piece
                    eval = minimax(board, depth + 1, True)
                    board[i][j] = EMPTY
                    min_eval = min(min_eval, eval)
        return min_eval

# 机器选择最佳下棋位置，并返回编号
def find_best_move_with_position(board):
    best_eval = float('-inf')
    best_move = (-1, -1)
    for i in range(BOARD_SIZE):
        for j in range(BOARD_SIZE):
            if board[i][j] == EMPTY:
                board[i][j] = machine_piece
                eval = minimax(board, 0, False)
                board[i][j] = EMPTY
                if eval > best_eval:
                    best_eval = eval
                    best_move = (i, j)
    return best_move, position_map[best_move]

# 发送数据到串口
def send_to_uart(angle, piece, position, countdown):
    color_code = 2 if piece == BLACK else 1  # 黑棋 2，白棋 1
    data = bytearray([0xff, 0xfe, int(angle * 2), color_code, position, countdown])
    print([0xff, 0xfe, int(angle * 2), color_code, position, countdown])
    uart.write(data)
    time.sleep(0.1)  # 延时 0.1s

# 检测棋盘状态
def detect_pieces(frame, centers):
    for index, center in enumerate(centers):
        x, y = int(center[0]), int(center[1])
        roi = frame[y-10:y+10, x-10:x+10]
        if roi.size == 0:
            continue
        
        hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
        mask_black = cv2.inRange(hsv, np.array([0, 0, 0]), np.array([180, 255, 70]))
        mask_white = cv2.inRange(hsv, np.array([0, 0, 180]), np.array([180, 20, 255]))
        
        if cv2.countNonZero(mask_black) > 50:
            color = BLACK  # 黑棋
        elif cv2.countNonZero(mask_white) > 50:
            color = WHITE  # 白棋
        else:
            color = EMPTY  # 空位
        
        row, col = divmod(index, 3)
        global_matrix[row, col] = color
        cv2.putText(frame, color, (x-10, y-15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)

def find_angle(corners):
    rect = cv2.minAreaRect(corners)
    angle = rect[-1]
    if angle < -45:
        angle += 90
    # 限制角度在-45度到45度之间
    if angle > 45:
        angle -= 90
    return angle

def find_qipan(cam, player_piece, machine_piece):
    while True:
        ret, frame = cam.read()
        if not ret:
            print("无法从摄像头读取帧")
            break

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        blurred = cv2.GaussianBlur(gray, (3, 3), 0)
        edged = cv2.Canny(blurred, 50, 150)
        contours, _ = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        if contours:
            largest_contour = max(contours, key=cv2.contourArea)
            epsilon = 0.02 * cv2.arcLength(largest_contour, True)
            approx = cv2.approxPolyDP(largest_contour, epsilon, True)
            if len(approx) == 4:
                corners = approx.reshape((4, 2))
                for corner in corners:
                    cv2.circle(frame, tuple(corner), 4, (0, 255, 0), -1)
                cv2.drawContours(frame, [approx], -1, (0, 255, 0), 2)
                
                cross_points = []
                for i in range(4):
                    for j in range(4):
                        cross_x = int((corners[0][0] * (3 - i) + corners[3][0] * i) * (3 - j) / 9 +
                                      (corners[1][0] * (3 - i) + corners[2][0] * i) * j / 9)
                        cross_y = int((corners[0][1] * (3 - i) + corners[3][1] * i) * (3 - j) / 9 +
                                      (corners[1][1] * (3 - i) + corners[2][1] * i) * j / 9)
                        cross_points.append((cross_x, cross_y))
                        cv2.circle(frame, (cross_x, cross_y), 3, (0, 255, 0), -1)
                
                centers = []
                for i in range(3):
                    for j in range(3):
                        center_x = int((cross_points[i * 4 + j][0] + cross_points[i * 4 + j + 1][0] +
                                        cross_points[(i + 1) * 4 + j][0] + cross_points[(i + 1) * 4 + j + 1][0]) / 4)
                        center_y = int((cross_points[i * 4 + j][1] + cross_points[i * 4 + j + 1][1] +
                                        cross_points[(i + 1) * 4 + j][1] + cross_points[(i + 1) * 4 + j + 1][1]) / 4)
                        centers.append((center_x, center_y))
                        cv2.circle(frame, (center_x, center_y), 2, (0, 255, 0), -1)
                
                centers = np.array(centers)
                if centers.shape[0] == 9:
                    cv2.imshow("Frame", frame)
                    if debounce(BUTTON_PIN_UPDATE):
                        detect_pieces(frame, centers)
                        print("当前棋盘状态：")
                        print_board(global_matrix)

                        # 检查是否有玩家赢了
                        if check_win(global_matrix, player_piece):
                            print("玩家赢了！")
                            send_to_uart(0, player_piece, 0, 0)  # 发送0表示游戏结束
                            break

                        # 检查是否有机器赢了
                        if check_win(global_matrix, machine_piece):
                            print("机器赢了！")
                            send_to_uart(0, machine_piece, 0, 0)  # 发送0表示游戏结束
                            break

                        # 检查是否平局
                        if not has_empty(global_matrix):
                            print("平局！")
                            send_to_uart(0, None, 0, 0)  # 发送0表示游戏结束
                            break

                        # AI下棋
                        if has_empty(global_matrix):
                            move, position = find_best_move_with_position(global_matrix)
                            if move == (-1, -1):
                                print("平局！")
                            else:
                                row, col = move
                                global_matrix[row][col] = machine_piece
                                print(f"机器下在位置 {position}")

                                # 计算旋转角度
                                angle = find_angle(corners)
                                X = int(angle)
                                print(f"Rotation angle: {angle:.2f} degrees")
                                # 发送数据到串口
                                send_to_uart(int(X + 90), machine_piece, position, 1)  # 1 表示倒计时开启
                                print(int(X + 90), machine_piece, position, 1)

                        print("更新后的棋盘状态：")
                        print_board(global_matrix)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

def main():
    global player_piece, machine_piece, cam

    print("请按下选择按键来选择棋子颜色：")
    print("按下 GPIO 9 对应的按键：玩家执白棋")
    print("按下 GPIO 13 对应的按键：玩家执黑棋")
    
    while True:
        if debounce(BUTTON_PIN_WHITE):
            player_piece = WHITE
            machine_piece = BLACK
            print("玩家选择了白棋")
            break
        elif debounce(BUTTON_PIN_BLACK):
            player_piece = BLACK
            machine_piece = WHITE
            print("玩家选择了黑棋")
            break

    print(f"玩家执 {player_piece}，机器执 {machine_piece}")
    print("等待更新按键确认棋盘状态...")

    # 初始化摄像头
    cam = cv2.VideoCapture(0)
    if not cam.isOpened():
        print("无法打开摄像头")
        return
    
    find_qipan(cam, player_piece, machine_piece)
if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        print(e)
    finally:
        if cam is not None:
            cam.release()
        cv2.destroyAllWindows()
